#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <kernel.h>
#include <vga.h>
#include <hardware.h>

dev_inst vga_text_dev;
currentscr vga_text_cscr;

void vga_out_mem_ch(screen * scr, int pos, char c)
{
    vga_out_mem_wch(scr, pos * 2, c, scr->default_attr);
}

void vga_out_mem_wch(screen * scr, int pos, char c, char attr)
{
    if (pos >= MAX_SCREEN_DATA_SIZE)
        return;

    scr->scrdata[pos] = c;
    scr->scrdata[pos + 1] = attr;
    if (ISCURRENTSCR(scr))
        {
        w_to_m(c, scr->base + pos);
        w_to_m(attr, scr->base + pos + 1);
        }
}

void vga_out_mem_str(screen * scr, int pos, char * s, size_t l)
{
    int i;

    for (i = 0; i < l; i++)
        {
        vga_out_mem_ch(scr, pos + i, s[i]);
        }

}

void vga_out_mem_wstr(screen * scr, int pos, char * s, size_t l)
{
    int i;

    for (i = 0; i < l; i += 2)
        {
        vga_out_ch_attr(scr, s[i], s[i+1]);
        }
}

char vga_in_mem_ch(screen * scr, int pos)
{
    short r;
    char c;

    r =  vga_in_mem_wch(scr, pos * 2);

    c = r & 0xFF;

    return c;
}

short vga_in_mem_wch(screen * scr, int pos)
{
    unsigned char c, d;
    short r;

    if (pos > MAX_SCREEN_DATA_SIZE)
        return 0;

    c = scr->scrdata[pos];

    d = scr->scrdata[pos + 1];

    r = (signed)(c + (d << 8));

    return r;
}

void vga_in_mem_str(screen * scr, int pos, char * s, size_t l)
{
    int i;

    if (pos * 2 < 0 || pos * 2 + l > MAX_SCREEN_DATA_SIZE)
        return;

    for (i = 0; i < l; i++)
        {
        s[i] = vga_in_mem_ch(scr, pos);
        }
}

void vga_in_mem_wstr(screen * scr, int pos, char * s, size_t l)
{
    if (pos < 0 || pos + l > MAX_SCREEN_DATA_SIZE)
        return;

    memcpy(s, scr->scrdata + pos, l);
}

void vga_out_ch(screen * scr, char c)
{
    vga_out_ch_attr(scr, c, scr->default_attr);
}

void vga_out_ch_attr(screen * scr, char c, char attr)
{
    char x, y;
    int pos;

    vga_text_get_cursor_pos(scr, &x, &y);

    pos = x * scr->size.x + y;

    switch(c)
        {
        case VGA_TEXT_BACK:
            if (y > 0)
                y--;
            break;
        case VGA_TEXT_CR:
            y = 0;
            break;
        case VGA_TEXT_LF:
            x++;
            break;
        default:
            pos = pos * 2;

            vga_out_mem_wch(scr, pos, c, attr);

            y++;
        }

    while (y >= scr->size.x)
        {
        x++;
        y -= scr->size.x;
        }

    if (x >= scr->size.y)
        {
        vga_text_roll(scr, x - scr->size.y + 1);
        x = scr->size.y - 1;
        }

    vga_text_set_cursor_pos(scr, x, y);

}

void vga_out_str(screen * scr, char * s, size_t l)
{
    int i;

    for (i = 0; i < l; i++)
        {
        vga_out_ch(scr, s[i]);
        }
}

void vga_out_str_attr(screen * scr, char * s, size_t l)
{
    int i;

    for (i = 0; i < l; i += 2)
        {
        vga_out_ch_attr(scr, s[i], s[i+1]);
        }

}

void vga_text_init()
{
    vga_text_dev.userData = &vga_text_cscr;

    vga_text_cscr.current = NULL;
    NODE_INIT(&vga_text_cscr.head);

    strcpy(vga_text_dev.devname, "VGAText");
    vga_text_dev.open = vga_text_open;
    vga_text_dev.close = vga_text_close;
    vga_text_dev.write = vga_text_write;
    vga_text_dev.read = vga_text_read;
    vga_text_dev.ioctl = vga_text_ioctl;

    hw_dev_add(&vga_text_dev);
}

void vga_text_clear(screen * scr)
{
    int i;
    char b[1024];

    for (i = 0; i < scr->size.x * 2; i += 2)
        {
        b[i] = ' ';
        b[i+1] = scr->default_attr;
        }
    for (i = 0; i < scr->size.y; i++)
        {
        vga_out_mem_wstr(scr, i * scr->size.x * 2, b, scr->size.x * 2);
        }

    vga_text_set_cursor_pos(scr, 0, 0);
}

void vga_text_roll(screen * scr, int n)
{
    char b[1024];
    int i, j;

    if (n >= scr->size.y || n <= -scr->size.y)
        {
        vga_text_clear(scr);
        return;
        }

    if (n == 0)
        return;

    if (n > 0)
        {
        j = 0;
        for (i = n; i < scr->size.y; i++, j++)
            {
            vga_in_mem_wstr(scr, i * 2 * scr->size.x, b, scr->size.x * 2);
            vga_out_mem_wstr(scr, j * 2 * scr->size.x, b, scr->size.x * 2);
            }

        for (i = 0; i < scr->size.x * 2; i += 2)
            {
            b[i] = ' ';
            b[i+1] = scr->default_attr;
            }

        for(j = j; j < scr->size.x; j++)
            {
            vga_out_mem_wstr(scr, j * scr->size.x, b, scr->size.x);
            }

        }
    else
        {
        j = scr->size.y;
        for (i = scr->size.y + n; i >= 0; i--, j--)
            {
            vga_in_mem_wstr(scr, i * 2 * scr->size.x, b, scr->size.x * 2);
            vga_out_mem_wstr(scr, j * 2 * scr->size.x, b, scr->size.x * 2);
            }

        for (i = 0; i < scr->size.x * 2; i += 2)
            {
            b[i] = ' ';
            b[i+1] = scr->default_attr;
            }

        for(j = j; j >= 0; j--)
            {
            vga_out_mem_wstr(scr, j * scr->size.x, b, scr->size.x);
            }

        }
}

void vga_text_set_cursor_pos(screen * scr, char x, char y)
{
    unsigned int pos;
    unsigned char l, h;

    scr->cursor.x = x;
    scr->cursor.y = y;

    if (ISCURRENTSCR(scr))
        {
        pos = (char)x * scr->size.x + y;

        pos = pos & 0xFFFF;

        l = pos & 0xFF;

        h = (pos >> 8) & 0xFF;

        int_lock();

        out_b(VGA_REG_ADDR, (char)VGA_TEXT_CURSOR_H);

        out_b(VGA_REG_DATA, h);

        out_b(VGA_REG_ADDR, (char)VGA_TEXT_CURSOR_L);

        out_b(VGA_REG_DATA, l);

        int_unlock();
        }
}

void vga_text_get_cursor_pos(screen * scr, char * x, char * y)
{
    *x = scr->cursor.x;
    *y = scr->cursor.y;
}

void vga_text_flush()
{
    screen * scr = (screen *)&vga_text_cscr;

    vga_text_set_cursor_pos(scr, scr->cursor.x, scr->cursor.y);
    write_to_real(scr->scrdata, scr->size.x * scr->size.y * 2, scr->base);
}

void * vga_text_open(void * handle, int mode, int flag)
{
    screen * scr;
    handle_inst * hd;

    hd = findhandle((int)handle);

    if (hd->handle != (int)handle || hd->dev == NULL)
        return (void *)ERROR;

    scr = malloc(sizeof(screen));

    if (scr == NULL)
        return (void *)ERROR;

    memset(scr, 0, sizeof(screen));

    scr->base = VGA_DEFAULT_TEXT_MEM;
    scr->mode = mode;
    scr->flag = flag;
    scr->size.x = VGA_DEFAULT_TEXT_WIDTH;
    scr->size.y = VGA_DEFAULT_TEXT_HEIGHT;
    scr->default_attr = VGA_DEFAULT_ATTR;

    NODE_ADDTAIL(scr, hd->dev->userData);

    hd->dev->userData = scr;

    return handle;
}

int vga_text_close(void * handle)
{
    screen * scr;
    handle_inst * hd;
    currentscr * cscr;

    hd = findhandle((int)handle);

    if (hd->handle != (int)handle || hd->dev == NULL || hd->userData == NULL || hd->dev->userData == NULL)
        return ERROR;

    scr = hd->userData;

    NODE_DEL(scr);

    cscr = hd->dev->userData;

    if (cscr->current == scr)
        cscr->current = NULL;

    free(scr);

    return OK;
}

ssize_t vga_text_read(void * handle, char * bfr, size_t n)
{
    return ERROR;
}

ssize_t vga_text_write(void * handle, char * bfr, size_t n)
{
    screen * scr;
    handle_inst * hd;
    ssize_t ret;
    int pos;

    hd = findhandle((int)handle);

    if (hd->handle != (int)handle || hd->dev == NULL || hd->userData == NULL)
        return ERROR;

    scr = hd->userData;

    switch(scr->mode)
        {
        case VGA_TEXT_MODE_STR:
            vga_out_str(scr, bfr, n);
            ret = n;
            break;
        case VGA_TEXT_MODE_RAW:
            n = (n >> 1) << 1;
            vga_out_str_attr(scr, bfr, n);
            ret = n;
            break;
        case VGA_MEM_MODE_STR:
            if (n <= sizeof(int))
                ret = ERROR;
            else
                {
                pos = *(int *)bfr;
                vga_out_mem_str(scr, pos, bfr, n);
                ret = n;
                }
            break;
        case VGA_MEM_MODE_RAW:
            if (n <= sizeof(int))
                ret = ERROR;
            else
                {
                n = (n >> 1) << 1;
                pos = *(int *)bfr;
                vga_out_mem_wstr(scr, pos, bfr, n);
                ret = n;
                }
            break;
        }
    return ret;
}

int vga_text_ioctl(void * handle, int mode, void * arg)
{
    int ret = OK;
    screen * scr;
    handle_inst * hd;
    currentscr * cscr;

    hd = findhandle((int)handle);

    if (hd->handle != (int)handle || hd->dev == NULL || hd->userData == NULL)
        return ERROR;

    scr = hd->userData;

    switch(mode)
        {
        case VGA_IOCTL_SHOW:
            if (!ISCURRENTSCR(scr))
                {
                cscr = (currentscr *)NODE_HEAD(scr);
                cscr->current = scr;
                vga_text_flush();
                }
            ret = OK;
            break;
        case VGA_IOCTL_FLUSH:
            vga_text_flush();
            ret = OK;
            break;

        case VGA_IOCTL_CLEAR:
            vga_text_clear(scr);
            ret = OK;
            break;
        }

    return ret;
}
